package 最后一块石头的重量II;

import java.util.Arrays;

/**
 * @author: AirMan
 * @date: 2025/5/8 10:44
 * @description: 题目难度：中等
 * 有一堆石头，每块石头的重量都是正整数。
 * 每一回合，从中选出任意两块石头，然后将它们一起粉碎。假设石头的重量分别为 x 和 y，且 x <= y。那么粉碎的可能结果如下：
 * 如果 x == y，那么两块石头都会被完全粉碎；
 * 如果 x != y，那么重量为 x 的石头将会完全粉碎，而重量为 y 的石头新重量为 y-x。
 * 最后，最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下，就返回 0。
 * 示例：
 * 输入：[2,7,4,1,8,1]
 * 输出：1
 * 解释：
 * 组合 2 和 4，得到 2，所以数组转化为 [2,7,1,8,1]，
 * 组合 7 和 8，得到 1，所以数组转化为 [2,1,1,1]，
 * 组合 2 和 1，得到 1，所以数组转化为 [1,1,1]，
 * 组合 1 和 1，得到 0，所以数组转化为 [1]，这就是最优值。
 */
public class Solution {
    public int lastStoneWeightII(int[] stones) {
        // 本题其实就是将石头分为重量尽可能相同的两堆，然后让这两堆石头进行相撞，剩下的石头就是最小的
        // 一堆的石头重量是sum，那么我们就尽可能拼成 重量为 sum / 2 的石头堆。 这样剩下的石头堆也是
        // 尽可能接近 sum/2 的重量。 那么此时问题就是有一堆石头，每个石头都有自己的重量，
        // 是否可以装满最大重量为 sum / 2的背包
        // ① dp数组及其含义：dp[i]表示背包容量为i时，能装的石头的最大重量
        // ② 状态转移公式：dp[i] = Math.max(dp[i], dp[i-stones[i] + stones[i]); 前提 i > stones[i]
        // ③ dp数组的初始化：将索引 stones[i] 到 sum / 2 之间的dp数组初始化为 stones[0]，其他初始化0
        // ④ 遍历顺序：因为当前的状态需要之前的状态进行确定，但是我们采用的使一维数组，如果从左向右遍历，会重复利用某一个石头
        //            所以dp数组的确定需要从右至左，这样当前状态的确定不会重复用石头，也不会影响前面还没有确定的状态
        //            如果采用的是二维数组的话，可以先遍历石头，再从左到右遍历背包的重量
        // ⑤ 举例推导dp数组：输入：[2,4,1,1]，此时target = (2 + 4 + 1 + 1)/2 = 4
        //       index:    0    1   2   3   4
        //                 0    0   2   2   2
        //                 0    0   2   2   4
        //                 0    1   2   3   4
        //                 0    1   2   3   4


        int sum = 0;
        for (int stone : stones) {
            sum += stone;
        }
        int target = sum / 2;
        // dp数组
        int[] dp = new int[target + 1];
        // init dp
        for (int i = stones[0]; i <= target; i++) {
            dp[i] = stones[0];
        }
//        System.out.println(Arrays.toString(dp));
        // complute dp max value( right -> left )
        for (int i = 1; i < stones.length; i++) {
            for (int j = target; j >= stones[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
//            System.out.println(Arrays.toString(dp));
        }
        return sum - dp[target] - dp[target];
    }
}
